package quirk;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * BitManager.java
 * 
 * This class is used to hold an arbitrarily long chunk of bits. Bits may be
 * added or retrieved from the data block in arbitrary lengths. This allows
 * for efficient compression of an output file. 
 * 
 * @author Zachary M. Allen
 */
public class BitManager {

	/**
	 * Current position in file
	 */
	private int position;
	
	/**
	 * The data array that holds the data chunk
	 */
	private char[] bitmask;
	
	/**
	 * Current size of bitmask
	 */
	private int size;

	/**
	 * Constructor
	 *
	 */
	public BitManager() {
		position = 0;
		bitmask = new char[1];
		size = bitmask.length;
	}
	
	/**
	 * Adds data to the data block - chains to void add(long, int)
	 * @param    data    Data to add
	 * @param    numBits    Number of bits to use to represent the new data
	 */
	public void addBits(int data, int numBits) {
		addBits((long) data, numBits);
	}
	
	/**
	 * Adds data to the data block
	 * @param    data    Data to add
	 * @param    numBits    Number of bits to use to represent the new data
	 */
	public void addBits(long data, int numBits) {
		
		if ((numBits + position) >= (8 * bitmask.length)) {
			bitmask = Util.resizeCharArray(bitmask, bitmask.length * 2);
		}
		
		long shift = numBits - 1;
        for (int i = position; i < position + numBits; i++) {
        	int index = i / 8;
        	long bit = ((data >> shift--) & 1);
        	bitmask[index] = (char) ((bitmask[index]) | bit << (7 - (i % 8)));
        }
        
        position += numBits;
        size = 1 + (position / 8);
		
	}
	
	/**
	 * Retrieves data bits from the block
	 * @param    firstBit    Position of first bit to retrieve
	 * @param    numBits    Number of bits to retrieve
	 * @return    Requested bits from the data block
	 */
	public long getBits(int firstBit, int numBits) {
		
		long retval = 0;
		
		for (int i = firstBit; i < firstBit + numBits; i++) {
			int index = i / 8;
			if (index >= bitmask.length) {
				return 0;
			}
			long bit = (bitmask[index] >> (7 - (i % 8))) & 1;
			retval = (retval << 1) | bit;
		}
		
		return retval;
		
	}
	
	/**
	 * Loads a file for dumping
	 * @param    file    File to load
	 * @throws    IOException
	 */
	public void load(String file) throws IOException {
		File f = new File(file);
		BufferedInputStream fis = new BufferedInputStream(new FileInputStream(f));
		bitmask = new char[(int) f.length()];
		for (int i = 0; i < f.length(); i++) {
			bitmask[i] = (char) fis.read();
		}
		fis.close();
	}

	/**
	 * Saves the data block to an output file
	 * @param    file    File to save
	 * @throws    IOException
	 */
	public void save(String file) throws IOException {
		BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(file));
		for (int i = 0; i < size; i++) {
			fos.write(bitmask[i]);
		}
		fos.close();
	}

}